package org.agent.aigccore.middleware.modbus.helper;

import com.alibaba.fastjson.JSON;
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.code.DataType;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.locator.BaseLocator;
import lombok.extern.slf4j.Slf4j;
import org.agent.aigccore.basic.Global;
import org.agent.aigccore.utils.FormatUtil;

import java.util.Objects;

/**
 * @author moshihong
 * @version TasModbusHelper.java, v 0.1 2025年09月02日 09:33 moshihong
 */
@Slf4j
public class TasModbusHelper {
    // 默认502
    public final int MODBUS_PORT = 502;

    // 从机地址（通常为1）
    public final static int SLAVE_ID = 1;

    /**
     * 判断Modbus是否连接
     *
     * @param deviceIp
     * @param modbusPort
     * @return
     */
    public static boolean isConnectModbus(String deviceIp, int modbusPort) {
        ModbusMaster tcpMaster = connectModbus(deviceIp, modbusPort);
        return Objects.isNull(tcpMaster) ? Boolean.FALSE : Boolean.TRUE;
    }

    /**
     * 建立Modbus TCP连接
     */
    public static ModbusMaster connectModbus(String deviceIp, int modbusPort) {
        String msg = FormatUtil.format("[IP={0}, PORT={1}]", deviceIp, modbusPort);
        try {
            IpParameters params = new IpParameters();
            params.setHost(deviceIp);
            params.setPort(modbusPort);
            ModbusMaster tcpMaster = new ModbusFactory().createTcpMaster(params, false);
            tcpMaster.setTimeout(Global.THREE * Global.THOUSAND);
            tcpMaster.setRetries(Global.TWO);
            tcpMaster.init();
            log.info("Modbus链接成功！{}, tcpMaster={}", msg, JSON.toJSONString(tcpMaster));
            return tcpMaster;
        } catch (Exception e) {
            log.error(FormatUtil.format("Modbus连接失败！参数={0}, msg={1}", msg, e.getMessage()), e);
        }
        return null;
    }

    /**
     * 读取单个寄存器（16位无符号整数）
     *
     * @param tcpMaster
     * @param tasRegisterAddr
     * @return
     * @throws ErrorResponseException
     * @throws ModbusTransportException
     */
    public static Number readRegister(ModbusMaster tcpMaster, int tasRegisterAddr) throws ErrorResponseException, ModbusTransportException {
        return readRegister(SLAVE_ID, tasRegisterAddr, tcpMaster);
    }

    /**
     * 读取单个寄存器（16位无符号整数）
     *
     * @param slaveId
     * @param tasRegisterAddr
     * @param tcpMaster
     * @return
     * @throws ErrorResponseException
     * @throws ModbusTransportException
     */
    private static Number readRegister(int slaveId, int tasRegisterAddr, ModbusMaster tcpMaster)
            throws ErrorResponseException, ModbusTransportException {
        // Modbus4j使用0基地址，转换公式：1基地址 -> 0基地址 = 1基地址 - 1
        BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, tasRegisterAddr - 1, DataType.TWO_BYTE_INT_UNSIGNED);
        return tcpMaster.getValue(locator);
    }

    /**
     * 向寄存器写入字符串（每个寄存器存储2个字符）
     *
     * @param tcpMaster
     * @param tasRegisterAddr
     * @param registerContent
     * @throws ErrorResponseException
     * @throws ModbusTransportException
     */
    public static void writeRegister(ModbusMaster tcpMaster, int tasRegisterAddr, String registerContent)
            throws ErrorResponseException, ModbusTransportException {
        byte[] bytes = registerContent.getBytes();
        int regIndex = 0;
        for (int i = 0; i < bytes.length; i += 2) {
            int regValue = 0;
            // 低8位：当前字节
            regValue |= (bytes[i] & 0xFF);
            // 高8位：下一个字节（若存在）
            if (i + 1 < bytes.length) {
                regValue |= (bytes[i + 1] & 0xFF) << 8;
            }
            tcpMaster.setValue(BaseLocator.holdingRegister(SLAVE_ID
                    , tasRegisterAddr - 1 + regIndex, DataType.TWO_BYTE_INT_UNSIGNED), regValue);
            regIndex++;
        }
        log.info("[writeRegister] 写入寄存器成功！registerContent={}", registerContent);
    }
}